#include <stdlib.h>
#include <string.h>

#include "server_type.h"
#include "fdset.h"
#include "platform.h"
#include "socketlibfunction.h"
#include "selectserver.h"

struct select_server_s
{
    struct server_s base_server;
    struct fdset_s*    fd_sets;
    struct array_s* connected_sockets;
    fd_set* read_result;
    fd_set* write_result;
};

static void select_server_poll(struct server_s* self)
{
    struct select_server_s* select_server = (struct select_server_s*)(self);
    struct fdset_s* fd_sets = select_server->fd_sets;

    if(fdset_poll(fd_sets, 1) > 0)
    {
        struct array_s* temp_array = select_server->connected_sockets;
        fd_set* temp_read_result = select_server->read_result;
        fd_set* temp_write_result = select_server->write_result;

        #if defined PLATFORM_WINDOWS
        int fd_count = temp_read_result->fd_count;
        int i = 0;
        for(; i < fd_count; ++i)
        {
            sock fd = temp_read_result->fd_array[i];
            if(FD_ISSET(fd, temp_read_result))
            {
                struct session_s* client = *(struct session_s**)array_at(temp_array, fd);
                server_enable_session_readable(self, client);
                fdset_del(fd_sets, fd, ReadCheck);
            }
        }

        fd_count = temp_write_result->fd_count;
        for(i = 0; i < fd_count; ++i)
        {
            sock fd = temp_write_result->fd_array[i];
            if(FD_ISSET(fd, temp_write_result))
            {
                struct session_s* client = *(struct session_s**)array_at(temp_array, fd);
                server_enable_session_writable(self, client);
                fdset_del(fd_sets, fd, WriteCheck);
            }
        }
        #else
        int max_num = array_num(temp_array);
        int i = 0;
        for( ; i < max_num; ++i)
        {
            struct session_s* client = *(struct session_s**)array_at(temp_array, i);
            if(NULL != client)
            {
                if(FD_ISSET(i, temp_read_result))
                {
                    server_enable_session_readable(self, client);
                    fdset_del(fd_sets, i, ReadCheck);
                }

                if(FD_ISSET(i, temp_write_result))
                {
                    server_enable_session_writable(self, client);
                    fdset_del(fd_sets, i, WriteCheck);
                }
            } 
        }
        #endif
    }
}

static void select_server_handler_io(struct server_s* self)
{
    server_handler_recv(self);
    server_handler_send(self);
}

static void select_server_halfclose(struct select_server_s* self, struct session_s* session)
{
    if(SOCKET_ERROR != session->fd && SOCKET_CONNECTED == session->status)
    {
        void* data = NULL;
        sock fd = session->fd;
        array_set(self->connected_sockets, fd, &data);
        fdset_del(self->fd_sets, fd, ReadCheck | WriteCheck | ErrorCheck);
        shutdown(fd, SD_BOTH);
        socket_close(fd);

        server_remove_readable(&self->base_server, session);
        server_remove_writable(&self->base_server, session);

        server_disable_session_writable(&self->base_server, session);
        server_disable_session_writable(&self->base_server, session);

        session->fd = SOCKET_ERROR;
    }
}

static void select_server_close(struct server_s* self, struct session_s* session)
{
    struct select_server_s* select_server = (struct select_server_s*)(self);
    select_server_halfclose(select_server, session);
    server_socket_onclose(self, session);
}

static bool select_server_recv(struct server_s* self, struct session_s* session)
{
    struct select_server_s* select_server = (struct select_server_s*)(self);
    bool del = false;
    int recv_result = session_recv(session);
    
    if(recv_result & RECV_OK)
    {
        struct buffer_s* buffer = session->recv_buffer;
        int total_len = buffer->write_pos-buffer->read_pos;
        int process_len = self->logic_on_recved(self, session->index, buffer->data+buffer->read_pos, total_len);

        if(process_len > 0 && process_len <= total_len)
        {
            buffer->read_pos += process_len;
        }
    }
    
    if(recv_result & RECV_CLOSE)
    {
        select_server_close(self, session);
        self->logic_on_close(self, session->index);
    }
    else
    {
        if(recv_result & RECV_EAGIN)
        {
            server_disable_session_readable(self, session);
            fdset_add(select_server->fd_sets, session->fd, ReadCheck);
            del = true;
        }
    }

    return del;
}

static bool select_server_send(struct server_s* self, struct session_s* session)
{
    struct select_server_s* select_server = (struct select_server_s*)(self);
    int send_result = session_send(session);
    bool del = false;

    if(send_result == SEND_CLOSE)
    {
        select_server_close(self, session);
        self->logic_on_close(self, session->index);
    }
    else
    {
        if(send_result & SEND_EAGIN)
        {
            server_disable_session_readable(self, session);
            fdset_add(select_server->fd_sets, session->fd, WriteCheck);
            del = true;
        }
    }

    return del;
}

static void select_server_enter(struct server_s* self, struct session_s* session)
{
    struct select_server_s* select_server = (struct select_server_s*)(self);
    struct array_s* connected_sockets = select_server->connected_sockets;
    int current_num = array_num(connected_sockets);
    int new_num = 0;
    sock fd = session->fd;
    void* data = NULL;

    if(current_num <= fd)
    {
        int i = current_num;
        array_increase(connected_sockets, fd-current_num+1);
        new_num = array_num(connected_sockets);
        for(; i < new_num; ++i)
        {
            array_set(connected_sockets, i, &data);
        }
    }

    array_set(connected_sockets, fd, &session);
    server_enable_session_writable(self, session);
    fdset_add(select_server->fd_sets, fd, ReadCheck);
}

struct server_s* selectserver_new(int num, logic_on_enter_pt logic_on_enter, logic_on_close_pt logic_on_close, logic_on_recved_pt logic_on_recved, int recv_buffer_size, int send_buffer_size)
{
    struct select_server_s* select_server_p = (struct select_server_s*)malloc(sizeof(struct select_server_s));
    struct server_s* base_server_p = NULL;

    if(NULL != select_server_p)
    {
        memset(select_server_p, 0, sizeof(*select_server_p));

        base_server_p = &select_server_p->base_server;

        if(server_construction(base_server_p, num, recv_buffer_size, send_buffer_size) &&
           NULL != (select_server_p->fd_sets = fdset_new()) &&
           NULL != (select_server_p->connected_sockets = array_new(num, sizeof(struct session_s*))))
        {
            struct array_s* connected_sockets_p = select_server_p->connected_sockets;
            int i = 0;
            void* data = NULL;
            for(; i < num; ++i)
            {
                array_set(connected_sockets_p, i, &data);
            }

            base_server_p->poll = select_server_poll;
            base_server_p->handler_io = select_server_handler_io;
            base_server_p->recv = select_server_recv;
            base_server_p->send = select_server_send;
            base_server_p->close = select_server_close;
            base_server_p->enter = select_server_enter;

            base_server_p->logic_on_enter = logic_on_enter;
            base_server_p->logic_on_close = logic_on_close;
            base_server_p->logic_on_recved = logic_on_recved;

            select_server_p->read_result = fdset_getresult(select_server_p->fd_sets, ReadCheck);
            select_server_p->write_result = fdset_getresult(select_server_p->fd_sets, WriteCheck);
        }
        else
        {
            selectserver_delete(base_server_p);
            base_server_p = NULL;
        }
    }

    return base_server_p;
}

void selectserver_delete(struct server_s* self)
{
    if(NULL != self)
    {
        struct select_server_s* select_server = (struct select_server_s*)(self);
        server_destroy(self);

        if(NULL != select_server->fd_sets)
        {
            fdset_delete(select_server->fd_sets);
            select_server->fd_sets = NULL;
        }
        
        if(NULL != select_server->connected_sockets)
        {
            array_delete(select_server->connected_sockets);
            select_server->connected_sockets = NULL;
        }
        
        select_server->read_result = NULL;
        select_server->write_result = NULL;

        free(select_server);
        select_server = NULL;
    }
}